﻿/*
 *  Copyright 2010 David Hawkes, daveh@cadlink.com
 *  Subject to the Apache License version 2.0 at http://www.apache.org/licenses/
 */

using System;

namespace DynamicAccess
{
  /// <summary>
  /// Wraps a dynamic object so it can be used by non dynamic languages such as managed C++
  /// </summary>
  public class DynWrap
  {
    /// <summary>
    /// Constructs a new wrapper
    /// </summary>
    /// <param name="dynValue">the dynamic value to be wrapped</param>
    public DynWrap(dynamic dynValue)
    {
      _dynValue = dynValue;
      _noThrow = false;
    }
    /// <summary>
    /// Constructs a new wrapper
    /// </summary>
    /// <param name="dynValue">the dynamic value to be wrapped</param>
    /// <param name="noThrow">if set to false attempts to silently ignore erros</param>
    public DynWrap(dynamic dynValue, bool noThrow)
    {
      _dynValue = dynValue;
      _noThrow = noThrow;
    }
    /// <summary>
    /// Call the dynamic object
    /// </summary>
    /// <param name="args">arguments for the call</param>
    /// <returns>wrapped return value from call</returns>
    public DynWrap C(params object[] args)
    {
      try {
        return new DynWrap(Accessors.Call(_dynValue, UnWrapArgs(args)), _noThrow);
      } catch {
        if(_noThrow)
          return null;
        else
          throw;
      }
    }
    /// <summary>
    /// Call the dynamic object member function
    /// </summary>
    /// <param name="name">member name</param>
    /// <param name="args">arguments for the call</param>
    /// <returns>wrapped return value from call</returns>
    public DynWrap CM(string name, params object[] args)
    {
      try {
        return new DynWrap(Accessors.CallMember(_dynValue, name, UnWrapArgs(args)), _noThrow);
      } catch {
        if(_noThrow)
          return null;
        else
          throw;
      }
    }
    /// <summary>
    /// Call the dynamic object member constructor
    /// </summary>
    /// <param name="args">arguments for the call</param>
    /// <returns>the wrapped constructed instance</returns>
    public DynWrap CC(params object[] args)
    {
      try {
        return new DynWrap(Accessors.CreateInstance(_dynValue, UnWrapArgs(args)), _noThrow);
      } catch {
        if(_noThrow)
          return null;
        else
          throw;
      }
    }
    /// <summary>
    /// The wrapped dynamic value
    /// </summary>
    public dynamic V
    {
      get { return _dynValue; }
      set { _dynValue = value; }
    }
    /// <summary>
    /// Index the wrapped dynamic object
    /// </summary>
    /// <param name="args">index arguments</param>
    /// <returns>wrapped indexed result</returns>
    public DynWrap I(params object[] args)
    {
      try {
        return new DynWrap(Accessors.GetIndex(_dynValue, UnWrapArgs(args)), _noThrow);
      } catch {
        if(_noThrow)
          return null;
        else
          throw;
      }
    }
    /// <summary>
    /// Sets an indexed value on the wrapped dynamic object
    /// </summary>
    /// <param name="args">index arguments and the value to set as the final item</param>
    public void SI(params object[] args)
    {
      try {
        Accessors.SetIndex(_dynValue, UnWrapArgs(args));
      } catch {
        if(!_noThrow)
          throw;
      }
    }
    /// <summary>
    /// Remove and indexed value from the wrapped dynamic object
    /// </summary>
    /// <param name="args">index arguments</param>
    public void RI(params object[] args)
    {
      try {
        Accessors.DeleteIndex(_dynValue, UnWrapArgs(args));
      } catch {
        if(!_noThrow)
          throw;
      }
    }
    /// <summary>
    /// Gets the chained members of the wrapped dynamic object
    ///   ie dw.M("m1", "m2") is equivalent to dw.V.m1.m2
    /// </summary>
    /// <param name="members">member names</param>
    /// <returns>wrapped member result</returns>
    public DynWrap M(params object[] members)
    {
      var obj = _dynValue;
      members = UnWrapArgs(members);
      foreach(var member in members) {
        try {
          obj = Accessors.GetMember(obj, (string) member);
        } catch {
          if(_noThrow)
            return null;
          else
            throw;
        }
      }
      return new DynWrap(obj, _noThrow);
    }
    /// <summary>
    /// Sets the chained members of the wrapped dynamic object
    /// </summary>
    /// <param name="members">member names and the value to set as the final item</param>
    public void SM(params object[] members)
    {
      var obj = _dynValue;
      members = UnWrapArgs(members);
      var member = (string)members[0];
      var membersLast = members.Length - 1;
      for(int i = 1; i < membersLast; ++i) {
        try {
          obj = Accessors.GetMember(obj, member);
          member = (string) members[i];
        } catch {
          if(!_noThrow)
            throw;
        }
      }
      Accessors.SetMember(obj, member, members[membersLast]);
    }
    /// <summary>
    /// Remove a member from the wrapped dynamic object
    /// </summary>
    /// <param name="member">member name to remove</param>
    public void RM(object member)
    {
      try {
        var dynWrap = member as DynWrap;
        if(dynWrap != null)
          member = dynWrap.V;
        Accessors.DeleteMember(_dynValue, (string) member);
      } catch {
        if(!_noThrow)
          throw;
      }
    }
    public static implicit operator bool(DynWrap dynWrap)
    {
      return Convert.ToBoolean(dynWrap._dynValue);
    }
    public static implicit operator sbyte(DynWrap dynWrap)
    {
      return Convert.ToSByte(dynWrap._dynValue);
    }
    public static implicit operator Int16(DynWrap dynWrap)
    {
      return Convert.ToInt16(dynWrap._dynValue);
    }
    public static implicit operator Int32(DynWrap dynWrap)
    {
      return Convert.ToInt32(dynWrap._dynValue);
    }
    public static implicit operator Int64(DynWrap dynWrap)
    {
      return Convert.ToInt64(dynWrap._dynValue);
    }
    public static implicit operator byte(DynWrap dynWrap)
    {
      return Convert.ToByte(dynWrap._dynValue);
    }
    public static implicit operator UInt16(DynWrap dynWrap)
    {
      return Convert.ToUInt16(dynWrap._dynValue);
    }
    public static implicit operator UInt32(DynWrap dynWrap)
    {
      return Convert.ToUInt32(dynWrap._dynValue);
    }
    public static implicit operator UInt64(DynWrap dynWrap)
    {
      return Convert.ToUInt64(dynWrap._dynValue);
    }
    public static implicit operator double(DynWrap dynWrap)
    {
      return Convert.ToDouble(dynWrap._dynValue);
    }
    public static implicit operator char(DynWrap dynWrap)
    {
      return Convert.ToChar(dynWrap._dynValue);
    }
    public static implicit operator string(DynWrap dynWrap)
    {
      return Convert.ToString(dynWrap._dynValue);
    }
    public static implicit operator DateTime(DynWrap dynWrap)
    {
      return Convert.ToDateTime(dynWrap._dynValue);
    }
    private object[] UnWrapArgs(object[] args)
    {
      for(var i = 0; i < args.Length; ++i) {
        var dynWrap = args[i] as DynWrap;
        if(dynWrap != null)
          args[i] = dynWrap.V;
      }
      return args;
    }
    private dynamic _dynValue;
    private bool _noThrow;
  }
}
